#include "session.h"

#include "tracer.h"
#include "config-fnt.h"
#include "graph.h"
#include "viewer.h"
#include "compute.thread.h"
#include "cache.thread.h"
#include "compute.thread.options.h"
#include "load.thread.h"
#include "listmodel.h"
#include "options.h"
#include "stream.h"
#include "misc.h"
#include "dialogs.h"
#include "options.dlg.h"
#include "plugin.manager.h"

#include <QtWidgets/QtWidgets>
#include <memory>
#include <array>
#include "session.options.h"
#include "sessionpriv.h"

void Session::attachOptionsDlg() { priv->attachOptionsDlg(); }
void Session::detachOptionsDlg() { priv->detachOptionsDlg(); }
SessionState Session::state() const { return priv->state; }
void Session::viewerReady(bool err) { priv->viewerReady(err); }

void Session::renameNeuron() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
		case SessionState::Computing:
			return;
		case SessionState::LoadingCubes:
		case SessionState::Ready:
			break;
	}
	if(priv->curRow<0 || static_cast<size_t>(priv->curRow)>=priv->graph.trees().size())
		return;
	Tree n=priv->graph.trees()[priv->curRow];
	bool ok;
	auto prevname=QString::fromStdString(n.name());
	auto name=QInputDialog::getText(this, "Rename Neuron", "Please give it a new name.", QLineEdit::Normal, prevname, &ok);
	if(!ok)
		return;
	if(prevname==name)
		return;
	auto cmd=Command::renameNeuron(priv, n, name.toStdString());
	priv->runCommand(cmd);
	Tracer::instance()->logMessage("STAT: ", "EDIT_NEURON_RENAME");
}

void Session::jumpPosition() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
			return;
		case SessionState::Computing:
		case SessionState::Ready:
		case SessionState::LoadingCubes:
			break;
	}
	Point pos=priv->curPos.point;
	if(!PositionDialog::getPosition(&pos, priv->bbox, priv->session))
		return;
	priv->jumpToPosition(Edge{}, -1, &pos);
	priv->updateActions();
	priv->viewer->update();
	if(priv->state==SessionState::Ready && priv->loadDataAuto)
		startDownloadCube();
	Tracer::instance()->logMessage("STAT: ", "EDIT_GOTO_POS");
}
void Session::jumpToTarget() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
			return;
		case SessionState::Ready:
		case SessionState::Computing:
		case SessionState::LoadingCubes:
			break;
	}
	if(!priv->tgtPos.point.valid())
		return;
	priv->jumpToPosition(priv->tgtPos.edge, priv->tgtPos.index, &priv->tgtPos.point);
	//priv->curPos.edge = priv->tgtPos.edge;
	//priv->curPos.index = priv->tgtPos.index+(priv->tgtPos.edge.points().size()- priv->tgtPos.index>10?10:0);
	//priv->pickPosition(priv->tgtPos.edge, priv->curPos.index, &priv->tgtPos.point);
	priv->updateActions();
	priv->viewer->update();
	if(priv->state==SessionState::Ready && priv->loadDataAuto)
		startDownloadCube();
	Tracer::instance()->logMessage("STAT: ", "EDIT_GOTO_TARGET");
}
void Session::pickCurrent() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
			return;
		case SessionState::Ready:
		case SessionState::Computing:
		case SessionState::LoadingCubes:
			break;
	}
	if(!priv->curPos.point.valid())
		return;
	priv->pickPosition(priv->curPos.edge, priv->curPos.index, &priv->curPos.point);
	priv->viewer->update();
	priv->updateActions();
	Tracer::instance()->logMessage("STAT: ", "EDIT_PICK_CURRENT");
}
void Session::startDownloadCube() {
	priv->startDownloadCube();
	Tracer::instance()->logMessage("STAT: ", "EDIT_LOAD_DATA");
}

void Session::toggleAutoDownload() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
			return;
		case SessionState::Computing:
		case SessionState::LoadingCubes:
		case SessionState::Ready:
			break;
	}
	priv->loadDataAuto=!priv->loadDataAuto;
	priv->actions[EDIT_LOAD_DATA_AUTO]->setText(priv->loadDataAuto ? "&Auto Load *":"&Auto Load");
	if(priv->loadDataAuto)
		startDownloadCube();
}
void Session::toggleAutoContrast() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
			return;
		case SessionState::Computing:
		case SessionState::LoadingCubes:
		case SessionState::Ready:
			break;
	}
	priv->contrastAuto=!priv->contrastAuto;
	priv->actions[EDIT_CONTRAST_AUTO]->setText(priv->contrastAuto ? "&Auto Contrast *":"&Auto Contrast");
}
void Session::toggleAutoConnect() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
			return;
		case SessionState::Computing:
		case SessionState::LoadingCubes:
		case SessionState::Ready:
			break;
	}
	priv->connectAuto=!priv->connectAuto;
	priv->actions[TRACING_CONN_AUTO]->setText(priv->connectAuto ? "&Auto Connect *":"&Auto Connect");
}
Command::Command(SessionPriv* tp, const QString& t):
	_sp{tp}, _title{t}, _performed{false}, _curChg{false}, _tgtChg{false}
{
	_curPos=tp->curPos;
	_tgtPos=tp->tgtPos;
}
void Command::updateBuffer(EdgePriv* ep) {
	_sp->viewer->updateEdge(ep);
}
void Command::updateBuffer(EdgePriv * ep, Point leftPoint, Point rightPoint)
{
	_sp->viewer->updateEdge(ep, leftPoint, rightPoint);

}
void Command::updateBuffer(EdgePriv* ep, size_t i) {
	_sp->viewer->updateEdgeNode(ep, i);
}
void Command::freeBuffer(EdgePriv* ep) {
	_sp->viewer->recycleEdge(ep);
}
Edge mapEdgeHelper(Edge esrc, size_t isrc, Edge edst, size_t dst0, bool dir, size_t* idst) {
	if(dir) {
		if(dst0-isrc<edst.points().size()) {
			*idst=dst0-isrc;
			return edst;
		}
	} else {
		if(isrc-dst0<edst.points().size()) {
			*idst=isrc-dst0;
			return edst;
		}
	}
	return Edge{};
}
void Command::mapEdgeMultiple(Edge e, std::vector<std::tuple<Edge, size_t, bool>> dats) {
	if(curPos().edge==e) {
		bool hit=false;
		for(auto& t: dats) {
			size_t eei;
			auto ee=mapEdgeHelper(e, curPos().index, std::get<0>(t), std::get<1>(t), std::get<2>(t), &eei);
			if(ee) {
				Position p{ee, eei, Point{}};
				setCurPos(p);
				hit=true;
				break;
			}
		}
		if(!hit) {
			Position p{Edge{}, 0, curPos().point};
			setCurPos(p);
		}
	}
	if(tgtPos().edge==e) {
		bool hit=false;
		for(auto& t: dats) {
			size_t eei;
			auto ee=mapEdgeHelper(e, tgtPos().index, std::get<0>(t), std::get<1>(t), std::get<2>(t), &eei);
			if(ee) {
				Position p{ee, eei, Point{}};
				setTgtPos(p);
				hit=true;
				break;
			}
		}
		if(!hit) {
			Position p{Edge{}, 0, tgtPos().point};
			setTgtPos(p);
		}
	}
}
void Command::mapEdge(Edge e, Edge ne, size_t i, bool dir) {
	mapEdgeMultiple(e, {std::make_tuple(ne, i, dir)});
}
void dumpGraph(Graph gr) {
	printMessage("\nNV %1 NE %2 NN %3\n", gr.vertices().size(), gr.edges().size(), gr.trees().size());
}
void Command::undo(SessionPriv* tp) {
	undoImp(tp);
	_performed=false;
	tp->path.points.clear();
	tp->viewer->setPathData(tp->path);
	tp->pickPosition(_tgtPos.edge, _tgtPos.index, &_tgtPos.point);
	tp->jumpToPosition(_curPos.edge, _curPos.index, &_curPos.point);
	dumpGraph(tp->graph);
};
void Command::redo(SessionPriv* tp) {
	redoImp(tp);
	_performed=true;
	tp->path.points.clear();
	tp->viewer->setPathData(tp->path);
	if(_tgtChg)
		tp->pickPosition(_tgtPosNew.edge, _tgtPosNew.index, &_tgtPosNew.point);
	if(_curChg)
		tp->jumpToPosition(_curPosNew.edge, _curPosNew.index, &_curPosNew.point);
	dumpGraph(tp->graph);
}
Graph Command::graph() const {
	return _sp->graph;
}
ListModel* Command::model() const {
	return _sp->listModel;
}
LabelListModel * Command::labelModel() const
{
	return _sp->labelListModel;

}
bool Command::isTargetPosOnPath() const {
	return _sp->pathConnected || _sp->curPos.point==_sp->tgtPos.point;
}
void Session::changeType() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
		case SessionState::Computing:
			return;
		case SessionState::Ready:
		case SessionState::LoadingCubes:
			break;
	}
	if(!priv->tgtPos.edge)
		return;
	int curr=priv->tgtPos.edge.type();
	if(curr) curr-=1; // (0, 2, 3, 4) -> (0, 1, 2, 3)
	bool ok;
	QStringList items;
	items<<"Undefined"<<"Axon"<<"Dendrite"<<"Apical dendrite";
	auto res=QInputDialog::getItem(this, "Choose neurite type", "Please choose a neurite type.", items, curr, false, &ok);
	if(!ok)
		return;
	curr=items.indexOf(res);
	if(curr==-1) {
		showWarning("Change edge type", "Wrong type: "+res, this);
		return;
	}
	if(curr) curr+=1; // (0, 1, 2, 3) -> (0, 2, 3, 4)
	if(curr==priv->tgtPos.edge.type())
		return;
	auto cmd=Command::changeType(priv, priv->tgtPos.edge, curr);
	priv->runCommand(cmd);
	Tracer::instance()->logMessage("STAT: ", "EDIT_CHANGE_TYPE");
}
void Session::changeMark() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
		case SessionState::Computing:
			return;
		case SessionState::Ready:
		case SessionState::LoadingCubes:
			break;
	}
	if(!priv->tgtPos.edge)
		return;
	int curr=priv->tgtPos.edge.points()[priv->tgtPos.index].m;
	bool ok;
	auto res=QInputDialog::getInt(this, "Set node mark", "Please choose an integer to mark the node.", curr, 0, 32767, 1, &ok);
	if(!ok)
		return;
	if(res<0) res=0;
	if(res>=32768) res=32767;
	if(res==curr)
		return;
	auto cmd=Command::changeMark(priv, priv->tgtPos.edge, priv->tgtPos.index, static_cast<int16_t>(res));
	priv->runCommand(cmd);
	Tracer::instance()->logMessage("STAT: ", "EDIT_CHANGE_MARK");
}
void Session::changeSize() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
		case SessionState::Computing:
			return;
		case SessionState::Ready:
		case SessionState::LoadingCubes:
			break;
	}
	if(!priv->tgtPos.edge)
		return;
	auto curr=priv->tgtPos.edge.points()[priv->tgtPos.index]._r;
	bool ok;
	auto res=QInputDialog::getDouble(this, "Set node size", "Please pick a node size (in um).", curr/256.0, 0, 255, 2, &ok);
	if(!ok)
		return;
	int v=lrint(res*256);
	if(v<0) v=0;
	if(v>=65536) v=65535;
	if(v==curr)
		return;
	auto cmd=Command::changeSize(priv, priv->tgtPos.edge, priv->tgtPos.index, static_cast<uint16_t>(v));
	priv->runCommand(cmd);
	Tracer::instance()->logMessage("STAT: ", "EDIT_CHANGE_SIZE");
}

void Session::createNeuron() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
		case SessionState::Computing:
			return;
		case SessionState::Ready:
		case SessionState::LoadingCubes:
			break;
	}
	if(!priv->tgtPos.edge)
		return;
	Vertex v;
	if(priv->tgtPos.index==0) {
		v=priv->tgtPos.edge.leftVertex();
	} else if(priv->tgtPos.index==priv->tgtPos.edge.points().size()-1) {
		v=priv->tgtPos.edge.rightVertex();
	}
	if(!v)
		return;
	if(v.tree()) {
		showWarning("Create neuron", "This vertex is already part of a neuron.", this);
		return;
	}
	auto cmd=Command::createNeuron(priv, v);
	priv->runCommand(cmd);
	Tracer::instance()->logMessage("STAT: ", "EDIT_NEURON_NEW");
}
void Session::removeNeurons() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
		case SessionState::Computing:
			return;
		case SessionState::Ready:
		case SessionState::LoadingCubes:
			break;
	}
	std::vector<size_t> idxes{};
	for(auto n: priv->graph.trees()) {
		if(n.selected())
			idxes.emplace_back(n.index());
	}
	if(idxes.size()<=0)
		return;
	auto cmd=Command::removeNeurons(priv, idxes);
	priv->runCommand(cmd);
	Tracer::instance()->logMessage("STAT: ", "EDIT_NEURON_REMOVE");
}
void Session::purgeNeurons() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
		case SessionState::Computing:
			return;
		case SessionState::Ready:
		case SessionState::LoadingCubes:
			break;
	}
	std::vector<size_t> idxes{};
	for(auto n: priv->graph.trees()) {
		if(n.selected())
			idxes.emplace_back(n.index());
	}
	if(idxes.size()<=0)
		return;
	auto cmd=Command::purgeNeurons(priv, idxes);
	priv->runCommand(cmd);
	Tracer::instance()->logMessage("STAT: ", "EDIT_NEURON_PURGE");
}
void Session::importSwc() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
		case SessionState::Computing:
			return;
		case SessionState::Ready:
		case SessionState::LoadingCubes:
			break;
	}

	QFileDialog dlg{this, "Please choose SWC files.", "", ""};
	dlg.setAcceptMode(QFileDialog::AcceptOpen);
	dlg.setFileMode(QFileDialog::ExistingFiles);
	dlg.setNameFilter("SWC files (*.swc)");
	if(dlg.exec()!=QDialog::Accepted)
		return;

	std::vector<Point> points;
	std::vector<int64_t> parents;
	try {
		for(auto& filename: dlg.selectedFiles()) {
			Tracer::instance()->logMessage("STAT: ", "Import SWC " + filename);
			std::map<int64_t, int64_t> idmap;
			// XXX use ifstream and move to shared?
			QFile sf_{filename};
			if(!sf_.open(QIODevice::ReadOnly)) {
				throwError("Failed to open file");
			}
			QTextStream sf{&sf_};

			while(!sf.atEnd()) {
				auto line=sf.readLine();
				if(line.isEmpty()) continue;
				if(line[0]=='#') continue;
				QTextStream fs(&line);
				fs.setRealNumberNotation(QTextStream::SmartNotation);
				fs.setRealNumberPrecision(13);
				int64_t id, par;
				int16_t type;
				double x, y, z, r;
				fs>>id>>type>>x>>y>>z>>r>>par;
				if(fs.status()!=QTextStream::Ok)
					throwError("Failed to parse line %1"+line);

				idmap[id]=points.size();
				points.emplace_back(x, y, z, r, type);
				if(par==-1) {
					parents.push_back(-1);
				} else {
					parents.push_back(idmap[par]);
				}
			}
		}
	} catch(const std::exception& e) {
		showWarning("Failed to import SWC", e.what(), this);
		return;
	}
	if(points.size()<=0)
		return;
	auto cmd=Command::importSwc(priv, points, parents);
	priv->runCommand(cmd);
	Tracer::instance()->logMessage("STAT: ", "EDIT_IMPORT_SWC");
}
void Session::importFnt() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
		case SessionState::Computing:
			return;
		case SessionState::Ready:
		case SessionState::LoadingCubes:
			break;
	}

	QFileDialog dlg(this, "Please choose FNT Session files.", "", "");
	dlg.setAcceptMode(QFileDialog::AcceptOpen);
	dlg.setFileMode(QFileDialog::ExistingFiles);
	dlg.setNameFilter("FNT Session files (*.fnt *.fntz)");
	if(dlg.exec()!=QDialog::Accepted)
		return;

	std::vector<bool> vertInfo{};
	std::vector<std::tuple<size_t, size_t, int16_t, std::vector<Point>>> edgInfo{};
	std::vector<std::tuple<size_t, std::string>> somaInfo{};
	try {
		for(auto& filename: dlg.selectedFiles()) {
			Tracer::instance()->logMessage("STAT: ", "Import FNT " + filename);
			auto vertIdxShift=vertInfo.size();

			InputStream fs;
			if(!fs.open(filename.toLocal8Bit().constData()))
				throwError("Failed to open file");
			fs.precision(13);
			fs.setf(std::ios::scientific);


			std::string line;
			fnt_getline(fs, line);
			if(!fs)
				throwError("Failed to read magic header");
			bool gzipped=false;
			if(line==FNTZ_MAGIC) {
				gzipped=true;
			} else if(line!=FNT_MAGIC) {
				throwError("Wrong magic: ", line);
			}

			fnt_getline(fs, line);
			if(!fs)
				throwError("Failed to read URL line");

			fnt_getline(fs, line);
			if(!fs)
				throwError("Failed to read line");
			while(line!="BEGIN_TRACING_DATA") {
				fnt_getline(fs, line);
				if(!fs)
					throwError("Failed to read line");
			}

			if(gzipped) {
				if(!fs.pushGzip())
					throwError("Failed to start decompressing");
			}
			size_t nv;
			fs>>nv;
			if(!fs)
				throwError("Failed to read size_t");
			for(size_t i=0; i<nv; i++) {
				int f;
				fs>>f;
				vertInfo.push_back(f);
				if(!fs)
					throwError("Failed to read bool");
			}

			size_t ne;
			fs>>ne;
			if(!fs)
				throwError("Failed to read size_t");
			for(size_t i=0; i<ne; i++) {
				size_t eli, eri, epl;
				int16_t type;
				fs>>type;
				fs>>eli;
				fs>>eri;
				fs>>epl;
				if(!fs)
					throwError("Failed to read ints");
				std::vector<Point> pts;
				for(size_t j=0; j<epl; j++) {
					pts.push_back(Point{});
					auto& p=pts.back();
					fs>>p.m>>p._x>>p._y>>p._z>>p._r;
					if(!fs)
						throwError("Failed to read point");
				}
				edgInfo.emplace_back(eli+vertIdxShift, eri+vertIdxShift, type, std::move(pts));
			}
			size_t nn;
			fs>>nn;
			if(!fs)
				throwError("Failed to read size_t");
			for(size_t i=0; i<nn; i++) {
				size_t si;
				fs>>si;
				std::string name;
				fnt_getline(std::ws(fs), name);
				if(!fs)
					throwError("Failed to read neuron");
				somaInfo.emplace_back(si+vertIdxShift, name);
			}
			if(gzipped) {
				if(!fs.pop())
					throwError("Failed to finish decompressing");
			}
			if(!fs.close())
				throwError("Failed to close file.");
		}
	} catch(const std::exception& e) {
		showWarning("Failed to import FNT", e.what(), this);
		return;
	}
	if(vertInfo.size()<=0 && edgInfo.size()<=0 && somaInfo.size()<=0)
		return;
	auto cmd=Command::importFnt(priv, vertInfo, edgInfo, somaInfo);
	priv->runCommand(cmd);
	Tracer::instance()->logMessage("STAT: ", "EDIT_IMPORT_FNT");

}

void Session::findPath() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
		case SessionState::Computing:
		case SessionState::LoadingCubes:
			return;
		case SessionState::Ready:
			break;
	}
	if(!priv->curPos.point.valid())
		return;
	Tracer::instance()->logMessage("STAT: ", "TRACING_FIND");
	priv->computeThread->find(priv->curPos, priv->graph);
	priv->setState(SessionState::Computing, "Finding a path...", true);

	auto pos = priv->curPos.point;
	QString curPos = "Current:" + QString::number(pos.x()) + ";" + QString::number(pos.y()) + ";" + QString::number(pos.z()) + ";";
	Tracer::instance()->logMessage("STAT: ", "Finding a path " + curPos );
}
void Session::connectPoints() {

	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
		case SessionState::Computing:
		case SessionState::LoadingCubes:
			return;
		case SessionState::Ready:
			break;
	}
	if(!priv->curPos.point.valid())
		return;
	if(!priv->tgtPos.point.valid())
		return;
	Tracer::instance()->logMessage("STAT: ", "TRACING_CONNECT");
	priv->computeThread->connect(priv->curPos, priv->tgtPos, priv->graph);
	priv->setState(SessionState::Computing, "Connect two points...", true);
	auto pos = priv->curPos.point;
	QString curPos ="Current:"+ QString::number(pos.x()) + ";" + QString::number(pos.y()) + ";" + QString::number(pos.z()) + ";";
	pos = priv->tgtPos.point;
	QString tgtPos = "Target:"+QString::number(pos.x()) + ";" + QString::number(pos.y()) + ";" + QString::number(pos.z()) + ";";
	Tracer::instance()->logMessage("STAT: ", "Connect two points " + curPos+" and "+ tgtPos);
}
void Session::changeConnectType()
{
	auto options = this->options();
	if (!QString::compare(options->getString("compute.connect.default"), "Simple")) {
		options->setString("compute.connect.default", "AStar");
		ComputeAlgOptions<ALG_CONN>::instance()->algorithmChanged(0);
		ComputeAlgOptions<ALG_CONN>::instance()->setChooser(0);
	}
	else {
		options->setString("compute.connect.default", "Simple");
		ComputeAlgOptions<ALG_CONN>::instance()->algorithmChanged(1);
		ComputeAlgOptions<ALG_CONN>::instance()->setChooser(1);
	}


	OptionsPage::computeConnOptions()->applyChanges();

}
void Session::refinePath() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
		case SessionState::Computing:
		case SessionState::LoadingCubes:
			return;
		case SessionState::Ready:
			break;
	}
	if(priv->path.points.size()<2)
		return;
	Tracer::instance()->logMessage("STAT: ", "TRACING_REFINE");
	priv->computeThread->refine(priv->path, priv->graph);
	priv->setState(SessionState::Computing, "Refining the path...", true);

}

Edge getNextPosition(Graph gr, const Position& curPos, size_t* idx) {
	// for lint
		for(auto e: gr.edges()) {
			if(e.type()!=0) {
				*idx=0;
				return e;
			}
		}

	struct pos_sort {
		Edge e;
		int l;
		size_t i;
		double d;
	};

	std::vector<pos_sort> poss{};
	for(auto v: gr.vertices()) {
		if(v.inLoop()) {
			bool atend=true;
			for(auto& p: v.neighbors()) {
				if(p.first.inLoop() && p.first.parentVertex()==v) {
					atend = false;
					break;
				}
			}
			if (atend) {
				auto e = v.parentEdge();
				pos_sort pos{ e, 0, 0 };
				if (e.leftVertex() != v) {
					pos.i = e.points().size() - 1;
				}
				pos.d = curPos.point.distTo(e.points()[pos.i]);
				if (pos.d > 0)
					poss.emplace_back(pos);
			}
		}
 else if (!v.finished()) {
	 int l;
	 if (curPos.edge && curPos.edge.tree()) {
		 if (v.tree()) {
			 if (v.tree() == curPos.edge.tree()) {
				 l = 1;
			 }
			 else {
				 l = 3;
			 }
		 }
		 else {
			 l = 2;
		 }
	 }
	 else {
		 if (v.tree()) {
			 l = 1;
		 }
		 else {
			 l = 2;
		 }
	 }
	 ////////////////////////////////////////////////
	 auto& ep = v.neighbors()[0];
	 auto e = ep.first;
	 pos_sort pos{ e, l, 0 };
	 if (ep.second) {
		 pos.i = e.points().size() - 1;
	 }
	 pos.d = curPos.point.distTo(e.points()[pos.i]);
	 if (pos.d > 0)
		 poss.emplace_back(pos);
		}
	}
	if (poss.size() <= 0) {
		return Edge{};
	}
	std::sort(poss.begin(), poss.end(), [](const pos_sort& a, const pos_sort& b)->bool { return a.l < b.l || (a.l == b.l && a.d < b.d); });
	*idx = poss[0].i;
	return poss[0].e;
}

void tranverse(Vertex &vert) {
	//vert.
}

Edge getNextUncheckedPosition(Graph gr, Position& curPos, size_t* idx) {

	struct pos_sort {
		Edge e;
		int l;
		size_t i;
		double d;
	};
	auto points = curPos.edge.points();
	auto point = points[curPos.index];
	int step = 5;
	auto options = Tracer::instance()->options();
	options->getInt("viewer.check.step", &step);
	// current edge

	if (curPos.index + 1 < points.size() && !points[curPos.index + 1].checked) {
		*idx = curPos.index + step > points.size() - 1 ? points.size() - 1 : curPos.index + step;
		return curPos.edge;
	}
	if (curPos.edge.checked() == 2)
	{
		for (size_t index = 0; index < points.size(); index++) {
			if (!points[index].checked) {
				*idx = index + step;
				curPos.index = index;
				curPos.point = points[index];
				return curPos.edge;
			}
		}
	}
	// neighbour first
	auto disLeft = curPos.point.distTo(curPos.edge.leftVertex().point());
	auto disRight = curPos.point.distTo(curPos.edge.rightVertex().point());
	Vertex vertex = disLeft > disRight ? curPos.edge.rightVertex() : curPos.edge.leftVertex();

	for (auto edge : vertex.neighbors()) {
		if (edge.first.checked() != 1) {
			*idx = 0;
			curPos.index = 0;
			curPos.edge = edge.first;

			curPos.point = curPos.edge.leftVertex().point();
			return curPos.edge;
		}
	}

	// computer order
	//tranverse(gr.trees()[0].root());
	
	// find other edge not checked
	bool allchecked = true;
	for (auto edge : gr.edges()) {
		if (!edge.rightVertex().checked() || !edge.leftVertex().checked() || !(edge.checked()))
		{
			allchecked = false;
			*idx = 0;
			for (int i = 0; i < edge.points().size(); i++)
			{
				if (edge.points()[i].checked == false) {
					*idx = i;
					break;
				}

			}
			return edge;
		}
	}
	if (allchecked) {

		*idx = 0;
		return  gr.edges()[0];
	}


}

void Session::finishBranch() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
		case SessionState::Computing:
			return;
		case SessionState::Ready:
		case SessionState::LoadingCubes:
			break;
	}
	Vertex v{};
	if(priv->curPos.edge) {
		if(priv->curPos.index==0) {
			v=priv->curPos.edge.leftVertex();
		} else if(priv->curPos.index==priv->curPos.edge.points().size()-1) {
			v=priv->curPos.edge.rightVertex();
		}
	}
	size_t nei;
	Edge ne{};
	if(v) {
		if(v.inLoop()) {
			if(v.parentEdge() && v.parentEdge().inLoop()) {
				ne=v.parentEdge();
				nei=0;
				if(ne.rightVertex()!=v)
					nei=ne.points().size()-1;
			} else {
				ne=getNextPosition(priv->graph, priv->curPos, &nei);
			}
		} else if(!v.finished()) {
			for (auto& p : priv->curPos.edge.points())
				p.checked = false;
			ne=getNextPosition(priv->graph, priv->curPos, &nei);
			auto cmd=Command::finishBranch(priv, v, ne, nei);
			priv->runCommand(cmd);
			Tracer::instance()->logMessage("STAT: ", "TRACING_FINISH");
			return;
		} else {
			ne=getNextPosition(priv->graph, priv->curPos, &nei);
		}
	} else {
		ne=getNextPosition(priv->graph, priv->curPos, &nei);
	}
	if(ne) {
		priv->jumpToPosition(ne, nei, nullptr);
		priv->updateActions();
		priv->viewer->update();
		if(priv->state==SessionState::Ready && priv->loadDataAuto)
			startDownloadCube();
	}
	auto pos = priv->curPos.point;
	QString qpos = QString::number(pos.x()) + ";" + QString::number(pos.y()) + ";" + QString::number(pos.z()) + ";";
	Tracer::instance()->logMessage("STAT: ", "Finish_branch at " + qpos);
}

void Session::checkedBranch() {
	int step = 5;
	auto options = Tracer::instance()->options();
	options->getInt("viewer.check.step", &step);
	switch (priv->state) {
	case SessionState::Invalid:
	case SessionState::LoadingCatalog:
	case SessionState::Readonly:
	case SessionState::Computing:
		return;
	case SessionState::Ready:
	case SessionState::LoadingCubes:
		break;
	}
	Vertex v{};
	if (priv->curPos.edge) {
		if (priv->curPos.index == 0) {
			v = priv->curPos.edge.leftVertex();
		}
		else if (priv->curPos.index == priv->curPos.edge.points().size() - 1) {
			v = priv->curPos.edge.rightVertex();
		}
	}
	size_t nei;
	Edge ne{};
	if (v && !v.finished()) {
		showWarning("Check Branch", "Finish the branch first!", this);
		return;
	}
	if( priv->curPos.edge&& !(priv->curPos.edge.checked()==1)) {

		ne = getNextUncheckedPosition(priv->graph, priv->curPos, &nei);
		if (ne != priv->curPos.edge) {
			auto cmd = Command::checkBranch(priv, v, ne, nei, nei+ step -1<ne.points().size()?nei+ step -1:nei);
			priv->runCommand(cmd);
			nei = nei + step < ne.points().size() ? nei + step : nei;
		}
		else {
			auto cmd = Command::checkBranch(priv, v, ne, nei, priv->curPos.index);
			priv->runCommand(cmd);
		}
		//Tracer::instance()->logMessage("STAT: ", "TRACING_CHECKED");
	}
	else if(priv->curPos.edge){
		ne = getNextUncheckedPosition(priv->graph, priv->curPos, &nei);
	}
	if (ne) {
		nei = nei  < ne.points().size() ? nei : ne.points().size()-1;
		priv->jumpToPosition(ne, nei, nullptr);
		priv->pickPosition(ne, nei, nullptr);
		priv->updateActions();
		priv->viewer->update();
		if (priv->state == SessionState::Ready && priv->loadDataAuto)
			startDownloadCube();
	}
	auto pos = priv->curPos.point;
	QString qpos = QString::number(pos.x()) + ";" + QString::number(pos.y()) + ";" + QString::number(pos.z()) + ";";
	Tracer::instance()->logMessage("STAT: ", "Check at " + qpos);
}

void Session::createBranch() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
		case SessionState::Computing:
			return;
		case SessionState::Ready:
		case SessionState::LoadingCubes:
			break;
	}
	if(priv->path.points.size()<2)
		return;
	if(priv->path.edge0 && priv->path.edge1) {
		Vertex v0{};
		if(priv->path.index0==0) {
			v0=priv->path.edge0.leftVertex();
		} else if(priv->path.index0==priv->path.edge0.points().size()-1) {
			v0=priv->path.edge0.rightVertex();
		}
		Vertex v1{};
		if(priv->path.index1==0) {
			v1=priv->path.edge1.leftVertex();
		} else if(priv->path.index1==priv->path.edge1.points().size()-1) {
			v1=priv->path.edge1.rightVertex();
		}
		if(v0 && v1 && v0==v1) {
			showWarning("Create branch", "Loops not allowed", this);
			return;
		} else if(!v0 && !v1 && priv->path.edge0==priv->path.edge1
				&& priv->path.index0==priv->path.index1) {
			showWarning("Create branch", "Loops not allowed", this);
			return;
		}
	}
	auto cmd=Command::createBranch(priv, priv->path);
	priv->runCommand(cmd);
	auto pos = priv->tgtPos.point;
	QString qpos = QString::number(pos.x()) + ";" + QString::number(pos.y()) + ";" + QString::number(pos.z()) + ";";
	Tracer::instance()->logMessage("STAT: ", "Tracing_branch at " + qpos);
}

void Session::extendBranch() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
		case SessionState::Computing:
			return;
		case SessionState::Ready:
		case SessionState::LoadingCubes:
			break;
	}
	if(priv->path.points.size()<2)
		return;
	if(priv->path.edge0 && priv->path.edge1) {
		Vertex v0{};
		if(priv->path.index0==0) {
			v0=priv->path.edge0.leftVertex();
		} else if(priv->path.index0==priv->path.edge0.points().size()-1) {
			v0=priv->path.edge0.rightVertex();
		}
		Vertex v1{};
		if(priv->path.index1==0) {
			v1=priv->path.edge1.leftVertex();
		} else if(priv->path.index1==priv->path.edge1.points().size()-1) {
			v1=priv->path.edge1.rightVertex();
		}
		if(v0 && v1 && v0==v1) {
			showWarning("Extend branch", "Loops not allowed", this);
			return;
		} else if(!v0 && !v1 && priv->path.edge0==priv->path.edge1
				&& priv->path.index0==priv->path.index1) {
			showWarning("Extend branch", "Loops not allowed", this);
			return;
		}
	}
	auto cmd=Command::extendBranch(priv, priv->path);
	priv->runCommand(cmd);
	auto pos = priv->tgtPos.point;
	QString qpos = QString::number(pos.x()) + ";" + QString::number(pos.y()) + ";" + QString::number(pos.z()) + ";";
	Tracer::instance()->logMessage("STAT: ", "Tracing_extend at "+ qpos);
}
static inline void setMarkImp(SessionPriv* tp, int16_t m) {
	auto pos = tp->tgtPos.point;
	QString qpos = QString::number(pos.x()) + ";" + QString::number(pos.y()) + ";" + QString::number(pos.z()) + ";";
	Tracer::instance()->logMessage("STAT: ", "Tracing_mark"+ QString::number(m)+" ", qpos);

	switch(tp->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
		case SessionState::Computing:
			return;
		case SessionState::Ready:
		case SessionState::LoadingCubes:
			break;
	}
	if(!tp->tgtPos.edge)
		return;
	if(tp->tgtPos.edge.points()[tp->tgtPos.index].m==m)
		return;

	auto cmd=Command::changeMark(tp, tp->tgtPos.edge, tp->tgtPos.index, static_cast<int16_t>(m));
	tp->runCommand(cmd);
}
void Session::setMark1() {
	setMarkImp(priv, 1);
}
void Session::setMark2() {
	setMarkImp(priv, 2);
}
void Session::setMark3() {
	setMarkImp(priv, 3);
}
void Session::setMark4() {
	setMarkImp(priv, 4);
}
void Session::clearMark() {
	setMarkImp(priv, 0);
}

void Session::removeEdge() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
		case SessionState::Computing:
			return;
		case SessionState::Ready:
		case SessionState::LoadingCubes:
			break;
	}
	if(!priv->curPos.edge && !priv->tgtPos.edge)
		return;
	auto cmd=Command::removeEdge(priv, priv->curPos.edge, priv->curPos.index, priv->tgtPos.edge, priv->tgtPos.index);
	priv->runCommand(cmd);
	Tracer::instance()->logMessage("STAT: ", "TRACING_DELETE");
}

void Session::abortOperation() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::Readonly:
		case SessionState::Ready:
			return;
		case SessionState::LoadingCatalog:
			priv->loadThread->cancel(); // XXX when cancelled, goto readonly
			break;
		case SessionState::Computing:
			priv->computeThread->cancel();
			Tracer::instance()->logMessage("STAT: ", "EDIT_ABORT_COMPUTE");
			break;
		case SessionState::LoadingCubes:
			CacheThread::instance()->cancel();
			priv->loadThread->cancel();
			Tracer::instance()->logMessage("STAT: ", "EDIT_ABORT_LOAD");
			//priv->setState(SessionState::Ready, "Finished.", false);

			break;
	}
}

void Session::saveCurrentCube() {
	if(!priv->cubes[0].data)
		return;
	QFileDialog dlg(this, "Please choose a NRRD file to save.", "", "");
	dlg.setAcceptMode(QFileDialog::AcceptSave);
	dlg.setDefaultSuffix("nrrd");
	dlg.setFileMode(QFileDialog::AnyFile);
	dlg.setNameFilter("NRRD file (*.nrrd)");
	if(dlg.exec()!=QDialog::Accepted)
		return;

	try {
		printMessage("Saving cubes...");
		OutputStream fs;
		if(!fs.open(dlg.selectedFiles()[0].toLocal8Bit().constData()))
			throwError("Cannot open file");
		fs.precision(13);
		fs.setf(std::ios::scientific);

		auto& data=priv->cubes[0].data;
		fs<<"NRRD0004\n";
		fs<<"# Created by " PACKAGE_NAME_LONG " " PACKAGE_VERSION "\n";
		switch(data.type()) {
			case CubeType::U8: fs<<"type: uint8\n"; break;
			case CubeType::I8: fs<<"type: int8\n"; break;
			case CubeType::U16: fs<<"type: uint16\n"; break;
			case CubeType::I16: fs<<"type: int16\n"; break;
			case CubeType::U32: fs<<"type: uint32\n"; break;
			case CubeType::I32: fs<<"type: int32\n"; break;
			case CubeType::U64: fs<<"type: uint64\n"; break;
			case CubeType::I64: fs<<"type: int64\n"; break;
			case CubeType::F32: fs<<"type: float\n"; break;
			case CubeType::F64: fs<<"type: double\n"; break;
			case CubeType::Unknown:
										throwError("Unknown cube type.");
		}

		fs<<"encoding: gzip\n";
		fs<<"dimension: 3\n";
		auto w=data.width();
		auto h=data.height();
		auto d=data.depth();
		fs<<"sizes: "<<w<<" "<<h<<" "<<d<<"\n";
		if(isLittleEndian()) {
			fs<<"endian: little\n";
		} else {
			fs<<"endian: big\n";
		}
		fs<<"\n";
		if(!fs)
			throwError("Failed to write header.");
		if(!fs.pushGzip())
			throwError("Failed to start gzip compressing.");

		auto ptr=data.data();
		auto ww=data.widthAdj();
		auto hh=data.heightAdj();
		auto bpv=bytes_per_voxel(data.type());
		for(int32_t z=0; z<d; z++) {
			for(int32_t y=0; y<h; y++) {
				fs.write(ptr+bpv*(y*ww+z*ww*hh), w*bpv);
			}
		}

		if(!fs.pop())
			throwError("Failed to finish gzip compressing.");
		if(!fs.close())
			throwError("Failed to close file.");
	} catch(const std::exception& e) {
		showError("Error saving cube", e.what(), this);
		return;
	}
}

void Session::takeScreenshot() {
	QFileDialog dlg(this, "Please choose a PNG file to save.", "", "");
	dlg.setAcceptMode(QFileDialog::AcceptSave);
	dlg.setDefaultSuffix("png");
	dlg.setFileMode(QFileDialog::AnyFile);
	dlg.setNameFilter("PNG files (*.png)");
	if(dlg.exec()!=QDialog::Accepted)
		return;

	auto img=priv->viewer->takeScreenshot();
	img.save(dlg.selectedFiles()[0]);
}

void Session::toggleRotate() {
	priv->viewer->toggleRotate();
}

void Session::setPositionCur(int v) {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
			return;
		case SessionState::Computing:
		case SessionState::Ready:
		case SessionState::LoadingCubes:
			break;
	}
	if(!priv->curPos.edge)
		return;
	if(v<0)
		return;
	if(static_cast<size_t>(v)==priv->curPos.index)
		return;
	priv->jumpToPosition(priv->curPos.edge, v, nullptr);
	priv->updateActions();
	priv->viewer->update();
	if(priv->state==SessionState::Ready && priv->loadDataAuto)
		startDownloadCube();
}
void Session::setPositionTgt(int v) {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
			return;
		case SessionState::Ready:
		case SessionState::Computing:
		case SessionState::LoadingCubes:
			break;
	}
	if(!priv->tgtPos.edge)
		return;
	if(v<0)
		return;
	if(priv->tgtPos.index==static_cast<size_t>(v))
		return;

	priv->pickPosition(priv->tgtPos.edge, v, nullptr);
	priv->viewer->update();
	priv->updateActions();
}

void Session::sourcesReady() {
	priv->sourcesReady();
}

template<typename T>
double todouble(T v);
template<>
double todouble<uint8_t>(uint8_t v) {
	return v/255.0;
}
template<>
double todouble<uint16_t>(uint16_t v) {
	return v/65535.0;
}
template<>
double todouble<float>(float v) {
	return v;
}

template<typename T>
double probXfuncImpl(const CubeDataRef& data) {
	auto w=data.width();
	auto h=data.height();
	auto d=data.depth();

	auto ptr=data.data();
	auto ww=data.widthAdj();
	auto hh=data.heightAdj();
	auto bpv=bytes_per_voxel(data.type());
	auto m=std::numeric_limits<T>::max();
	for(int32_t z=0; z<d; z+=3) {
		for(int32_t y=0; y<h; y+=3) {
			auto pp=reinterpret_cast<const T*>(ptr+bpv*(y*ww+z*ww*hh));
			for(int32_t x=0; x<w; x++) {
				if(pp[x]<m)
					m=pp[x];
			}
		}
	}
	auto r=todouble<T>(m);
	printMessage("min: ", r);
	return r;
}
double probXfunc(const Cube& cube) {
	auto& data=cube.data;
	switch(data.type()) {
			case CubeType::U8:
				return probXfuncImpl<uint8_t>(data);
			case CubeType::U16:
				return probXfuncImpl<uint16_t>(data);
			case CubeType::F32:
				return probXfuncImpl<uint16_t>(data);
			default:
				break;
	}
	return NAN;
}

void Session::cubeFinished() {
	auto cubes=priv->loadThread->getOutput();
	bool need_upd{false};
	bool upd_bbox{false};
	for(auto& cube: cubes) {
		if(cube.second.data) {
			if(priv->updateBoundingBox(cube.second))
				upd_bbox=true;
			for(int i=0; i<9; i++) {
				if(priv->cube_maps[i]==cube.first && !cube.second.data.cubeId().annotTree) {
					priv->cubes[i]=cube.second;
					need_upd=true;
					if(i%3==0)
						priv->computeThread->updateCube(i/3, cube.second, priv->xfuncs[i]);
					if(i==0) {
						auto lb=probXfunc(cube.second);
						if(!std::isnan(lb))
							priv->xfunc->setPositions(lb, priv->xfuncs[i].second);
					}
					priv->viewer->updateCube(i, cube.second, priv->xfuncs[i]);
					printMessage("cube ready: ", priv->channels[cube.first], "->", i);
				}
			}
			if(priv->annot_map==cube.first && cube.second.data.cubeId().annotTree) {
				priv->updateAnnotModel(cube.second.data.cubeId().annotTree);
				priv->annotCube=cube.second;
				need_upd=true;
				//priv->viewer->updateCube(i, cube.second, priv->xfuncs[i]);
				printMessage("cube ready: ", priv->channels[cube.first], "->");
			}
		} else {
			showWarning("Failed to download source: ", priv->channels[cube.first], priv->session);
		}
	}
	if(upd_bbox)
		priv->viewer->updateBoundingBox(priv->bbox);
	if(need_upd)
		priv->viewer->update();
}
void Session::updateProgress(int cur, int tot) {
	printMessage("Progress: ", cur, "/", tot);
	priv->progr->setMaximum(tot);
	priv->progr->setValue(cur);

	if(cur==tot && priv->loadThread->reallyFinished()) {
		QApplication::alert(this);
		switch(priv->state) {
			case SessionState::Invalid:
			case SessionState::Readonly:
				return;
			case SessionState::LoadingCatalog:
				//
				// XXX put sourcesReady here
				break;
			case SessionState::Computing:
				// XXX
				break;
			case SessionState::LoadingCubes:
				priv->setState(SessionState::Ready, "ready", false);
				break;
			case SessionState::Ready:
				break;
		}
	}
}

void Session::timeoutFiveMinute() {
	if(priv->timer_check!=priv->ver) {
		Tracer::instance()->logMessage("STAT: ", "TIMEOUT");
		priv->dumpGraphDetail();
		priv->timer_check=priv->ver;
	}
}

void Session::computeFinished() {
	Command* cmd = nullptr;
	switch(priv->state) {
		case SessionState::Computing:
			if(priv->computeThread->getPath(&priv->path, &priv->pathConnected)) {
				double len=0;
				for(size_t i=1; i<priv->path.points.size(); i++) {
					len+=priv->path.points[i].distTo(priv->path.points[i-1]);
				}
				printMessage("Total length: ", len);
				priv->viewer->setPathData(priv->path);
				if (priv->computeThread->algType() == AlgType::ALG_FIND) {
					cmd = Command::extendBranch(priv, priv->path);
					priv->runCommand(cmd);
					pickCurrent();
					priv->tgtPos = priv->curPos;

					jumpToTarget();

				}
			}
			priv->viewer->setProgressData({});
			priv->viewer->update();

			priv->setState(SessionState::Ready, "Finished.", false);
			break;
		default:
			break;
	}
	if (priv->loadDataAuto)
		startDownloadCube();
}




void Session::showPopupMenu(const QPoint& pt) { priv->popupViewer->popup(pt); }
Options* Session::options() { return &priv->options; }
void Session::updateActions() { priv->updateActions(); }

/* misc UIs */
void Session::currentChanged(const QModelIndex& selected, const QModelIndex& deselected) {
	printMessage("current %1", priv->curRow);
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
			return;
		case SessionState::Readonly:
		case SessionState::Computing:
		case SessionState::LoadingCubes:
		case SessionState::Ready:
			break;
	}
	if(selected.isValid()) {
		if(priv->curRow!=selected.row()) {
			priv->curRow=selected.row();
			auto& p=priv->graph.trees()[priv->curRow].root().neighbors()[0];
			priv->pickPosition(p.first, p.second?p.first.points().size()-1:0, nullptr);
		}
	} else {
		priv->curRow=-1;
	}
	priv->updateActions();
	priv->viewer->update();
}
void Session::labelSelectionChanged(const QItemSelection & selected, const QItemSelection & deselected)
{
	auto index = selected.indexes();
	if (index.size()) {
		priv->curPos.point = priv->labelListModel->m_markPoints[index[0].row()];
		jumpPosition();
	}
}
void Session::labelCurrentChanged(const QModelIndex & selected, const QModelIndex & deselected)
{

}
void Session::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
			return;
		case SessionState::Readonly:
		case SessionState::Computing:
		case SessionState::LoadingCubes:
		case SessionState::Ready:
			break;
	}
	auto gp=GraphPriv::get(priv->graph);
	for(auto& idx: selected.indexes()) {
		auto np=TreePriv::get(gp->trees[idx.row()]);
		np->selected=true;
	}
	for(auto& idx: deselected.indexes()) {
		auto np=TreePriv::get(gp->trees[idx.row()]);
		np->selected=false;
	}
	priv->updateActions();
	priv->viewer->update();
	auto p =Point();
	priv->labelListModel->update(p);
}
/* thread callback */
void Session::showComputeProgress() {
	auto& dat=priv->computeThread->progressData();
	priv->viewer->setProgressData(dat);
	priv->computeThread->progressDataUpdated();
	priv->viewer->update();
}
void Session::computeThreadWarning(const QString& msg) {
	showWarning("Compute thread", msg, priv->session);
}
void Session::computeThreadError(const QString& msg) {
	showError("Compute thread", msg, priv->session);
	priv->setState(SessionState::Readonly, "Readonly mode due to error in threads", false);
}
void Session::loadThreadWarning(const QString& msg) {
	showWarning("Load thread", msg, priv->session);
}
void Session::loadThreadError(const QString& msg) {
	showError("Error in load thread", msg, priv->session);
	priv->setState(SessionState::Readonly, "Readonly mode due to error in threads", false);
}
void Session::pickPosition(Edge edg, size_t idx, const Point* pos) {
	priv->pickPosition(edg, idx, pos);
	priv->updateActions();
	priv->connectIfPossible();
}
void Session::jumpToPosition(Edge edg, size_t idx, const Point* pos) {
	priv->jumpToPosition(edg, idx, pos);
}

Session::Session(const LoadThreadInput& input):
	QMainWindow{nullptr, 0}, priv{new SessionPriv{this, input}}
{
	priv->loadOptions();
	priv->setupActions();
	priv->setupMenus();
	priv->setupSidePanel();
	priv->setupAnnotPanel();
	priv->setupStatusBar();
	priv->setupMenuBarAndToolBar();
}
Session::Session(const QString& file):
	QMainWindow{nullptr, 0}, priv{new SessionPriv{this, file}}
{
	priv->loadOptions();
	priv->setupActions();
	priv->setupMenus();
	priv->setupSidePanel();
	priv->setupAnnotPanel();
	priv->setupStatusBar();
	priv->setupMenuBarAndToolBar();
}
Session::Session(const QString& file, const QString& swap):
	QMainWindow{nullptr, 0}, priv{new SessionPriv{this, file, swap}}
{
	priv->loadOptions();
	priv->setupActions();
	priv->setupMenus();
	priv->setupSidePanel();
	priv->setupAnnotPanel();
	priv->setupStatusBar();
	priv->setupMenuBarAndToolBar();
}
Session::~Session() {
	delete priv->viewer;
	delete priv;
}


void Session::saveSession() { priv->saveSession(); }
void Session::saveSessionAs() { priv->saveSessionAs(); }
void Session::readGraph() {
	priv->readGraph(sender());
}
void Session::closeEvent(QCloseEvent* event) {
	Q_EMIT requestCloseSession(this);
	event->ignore();
}
void Session::closeSession() {
	Q_EMIT requestCloseSession(this);
}
bool Session::closeSessionImp() {
	return priv->closeSessionImp();
}

void Session::undoCommand() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
		case SessionState::Computing:
			return;
		case SessionState::LoadingCubes:
		case SessionState::Ready:
			break;
	}
	if(priv->undoList.size()<=0)
		return;
	auto cmd=priv->undoList.front();
	priv->undoList.pop_front();
	cmd->undo(priv);
	priv->redoList.push_front(cmd); // *graph *cur *tgt
	priv->updateVersion(-1, false);
	priv->updateActions();
	priv->viewer->update();
	if(priv->state==SessionState::Ready && priv->loadDataAuto)
		startDownloadCube();
	Tracer::instance()->logMessage("STAT: ", "EDIT_UNDO");
}
void Session::redoCommand() {
	switch(priv->state) {
		case SessionState::Invalid:
		case SessionState::LoadingCatalog:
		case SessionState::Readonly:
		case SessionState::Computing:
			return;
		case SessionState::Ready:
		case SessionState::LoadingCubes:
			break;
	}
	if(priv->redoList.size()<=0)
		return;
	auto cmd=priv->redoList.front();
	priv->redoList.pop_front();
	cmd->redo(priv); // *graph *cur *tgt
	priv->undoList.push_front(cmd);
	priv->updateVersion(1, false);
	priv->updateActions();
	priv->viewer->update();
	if(priv->state==SessionState::Ready && priv->loadDataAuto)
		startDownloadCube();
	Tracer::instance()->logMessage("STAT: ", "EDIT_REDO");
}

////#include "session.ui.cc"
//#include "session.options.cc"
